package com.arpa.wms.common.util;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.LogUtil;
import com.arpa.ntocc.common.common.util.RedisUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.ntocc.common.service.IShipmentService;
import com.arpa.wms.domain.entity.Inventory;
import com.arpa.wms.domain.enums.OrderIdPrefixEnum;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 单号生成工具类
 *
 * @Author xuyang
 * @Date 2020-8-27 11:20:52
 * <p>
 * <p>
 * 所有单号格式调整：
 * 单号组成部分有：类型简写，货主编号，年月日（yyyyMMdd），流水号（每天重新计数）
 * <p>
 * 单号需要保持一定的顺序性，使用单节点redis进行计数
 */
@Component
@Log4j2
public class OrderIdGeneratorUtil {

    private OrderIdGeneratorUtil() {
        throw new IllegalStateException("Utility class");
    }

    private static IShipmentService iShipmentService;

    @Autowired
    public OrderIdGeneratorUtil(IShipmentService iShipmentService) {
        OrderIdGeneratorUtil.iShipmentService = iShipmentService;
    }

    /**
     * 拼装redis的key前缀
     */
    private static final String ORDER_ID_REDIS_PREFIX = "orderId";

    /**
     * 过期时间，单位为秒，86400秒（1天）; 先设置30天过期，以备测试查看
     */
    private static final int EXPIRE_SECONDS = 86400 * 30;

    /**
     * wms根据订单前缀、货主编号生成单据号
     *
     * @param orderTypeEnum
     * @param shipmentCode
     * @return
     */
    public static String generatorOrderId(OrderTypeEnum orderTypeEnum, String shipmentCode, String groupCode) {
        // 添加默认参数，默认前缀是WMS的
        return generatorOrderId(orderTypeEnum, shipmentCode, OrderIdPrefixEnum.WMS,groupCode);
    }
    /**
     * wms根据订单前缀、货主编号生成单据号
     *
     * @param orderTypeEnum
     * @return
     */
    public static List<String> generatorOrderId(OrderTypeEnum orderTypeEnum,int count, String groupCode) {
        // 添加默认参数，默认前缀是WMS的
        return generatorOrderId(orderTypeEnum, OrderIdPrefixEnum.WMS,count,groupCode);
    }

    /**
     * oms根据订单前缀、货主编码生成单据号
     * @param orderTypeEnum
     * @param shipmentCode
     * @return
     */
    public static String generatorOmsOrderId(OrderTypeEnum orderTypeEnum, String shipmentCode, String groupCode) {
        // 前缀使用oms的
        return generatorOrderId(orderTypeEnum, shipmentCode, OrderIdPrefixEnum.OMS,groupCode);
    }

    /**
     * 根据订单前缀、货主编号生成单据号
     *
     * @param orderTypeEnum
     * @param shipmentCode
     * @param prefix，       前缀，用于避免WMS、OMS相同前缀的订单使用同一个计数器
     * @return
     */
    public static String generatorOrderId(OrderTypeEnum orderTypeEnum, String shipmentCode, OrderIdPrefixEnum prefix, String groupCode) {
        if (orderTypeEnum == null) {
            throw new ServiceException("获取单号失败，单号类型不能为空");
        }
        String dateStr = DateUtil.getDate();
        // 添加prefix前缀，用于区分wms、oms等不同端
        //在生成redisKey的地方添加组织机构   2021.11.26
        String redisKey = ORDER_ID_REDIS_PREFIX + prefix.getValue() + orderTypeEnum.getCode() + dateStr + groupCode;
        Long num = RedisUtil.incr(redisKey);
        if (null == num) {
            //@todo redis返回异常数据，需要后期备灾处理，例如查询数据库等
            String msg = "redis服务器返回单号异常,单号类型:" + orderTypeEnum.getCode();
            log.info(LogUtil.log("orderIdGenerator", msg));
            throw new ServiceException(msg);
        }
        // 电商单日流水较多，单号流水暂时扩充到百万级别
        if (num < 7) {
            RedisUtil.expire(redisKey, EXPIRE_SECONDS);
        }

        // 根据货主code，获取货主的编码  XYTODO 再次屏蔽掉，待确认后再考虑是否使用货主编码
        String shipmentSerialNumber = "";
        /*if(StrUtil.isNotBlank(shipmentCode)) {
            Shipment shipment = iShipmentService.getOne(new QueryWrapper<Shipment>().lambda().eq(Shipment::getPartyCode, shipmentCode));
            if (shipment != null) {
                shipmentSerialNumber = StrUtil.blankToDefault(shipment.getSerialNumber(), "");
            }
        }*/

        return orderTypeEnum.getCode() + shipmentSerialNumber + dateStr + String.format("%0" + 7 + "d", num);
    }

    /**
     * 根据订单前缀、货主编号生成单据号
     *
     * @param orderTypeEnum
     * @param prefix，       前缀，用于避免WMS、OMS相同前缀的订单使用同一个计数器
     * @return
     */
    public static List<String> generatorOrderId(OrderTypeEnum orderTypeEnum,  OrderIdPrefixEnum prefix, int count, String groupCode) {
        if (orderTypeEnum == null) {
            throw new ServiceException("获取单号失败，单号类型不能为空");
        }
        String dateStr = DateUtil.getDate();
        // 添加prefix前缀，用于区分wms、oms等不同端
        String redisKey = ORDER_ID_REDIS_PREFIX + prefix.getValue() + orderTypeEnum.getCode() + dateStr + groupCode;
        Long numBegin = RedisUtil.incr(redisKey);
        Long numEnd = RedisUtil.incrby(redisKey,count-1);
        if (null == numBegin) {
            //@todo redis返回异常数据，需要后期备灾处理，例如查询数据库等
            String msg = "redis服务器返回单号异常,单号类型:" + orderTypeEnum.getCode();
            log.info(LogUtil.log("orderIdGenerator", msg));
            throw new ServiceException(msg);
        }
        // 电商单日流水较多，单号流水暂时扩充到百万级别
        if (numEnd < 7) {
            RedisUtil.expire(redisKey, EXPIRE_SECONDS);
        }

        // 根据货主code，获取货主的编码  XYTODO 再次屏蔽掉，待确认后再考虑是否使用货主编码
        String shipmentSerialNumber = "";
        List<String> codes = new ArrayList<>();
        for(;numBegin<=numEnd;numBegin++){
            codes.add(orderTypeEnum.getCode() + shipmentSerialNumber + dateStr + String.format("%0" + 7 + "d", numBegin));
        }

        return codes;
    }


    private final static String RECEIVE_LOT_REDIS_KEY = "receive_lot";

    /**
     * 生成收货批号
     *
     * @return
     */
    public static String generatorReceiveLot(String warehouseCode) {
        String dateStr = DateUtil.getDate();
        String redisKey = RECEIVE_LOT_REDIS_KEY + warehouseCode;
        Long num = RedisUtil.incr(redisKey);
        if (num < 3) {
            RedisUtil.expire(redisKey, EXPIRE_SECONDS);
        }
        String receiveLot = dateStr + String.format("%0" + 3 + "d", num);
        return receiveLot;
    }

    public enum OrderTypeEnum {
        /**
         * 订单类型 枚举类
         */
        SH("SH", "SH", "收货单"),
        PA("PA", "PA", "上架单"),
        RK("RK", "RK", "入库单"),
        FH("FH", "FH", "出库订单"),
        PK("PK", "PK", "拣货单"),
        PD("PD", "PD", "盘点单"),
        LY("LY", "LY", "领用单"),
        CK("CK", "CK", "出库单"),
        BS("BS", "BS", "报损单"),
        MO("MO", "MO", "移位单"),
        QC("QC", "QC", "期初单"),
        PO("PO", "PO", "OMS采购单"),
        SK("SK", "SK", "WMS收款单"),
        FK("FK", "FK", "OMS付款单"),
        CY("CY", "CY", "差异单"),
        PT("PT", "PT", "OMS采购退单"),
        SO("SO", "SO", "OMS销售单"),
        ST("ST", "ST", "OMS销售退单"),
        DB("DB", "DB", "调拨单"),
        BC("BC", "BC", "波次单"),
        PACK("PACK", "PACK", "打包任务单"),
        PP("PP", "PP", "预打包单"),
        MG("MG", "MG", "OMS合并订单"),
        ;

        private final String code;
        private final String type;
        private final String name;

        OrderTypeEnum(String code, String type, String name) {
            this.code = code;
            this.name = name;
            this.type = type;
        }

        public String getCode() {
            return code;
        }

        public String getType() {
            return type;
        }

        public String getName() {
            return name;
        }
    }

    /**
     * 根据库存信息生成批次号
     *
     * @param inv
     * @return
     */
    public static String generatorLot(Inventory inv) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
        StringBuilder lot = new StringBuilder();

        // 库存批次，由12个字段组成
        if (StrUtil.isNotBlank(inv.getReceiveLot())) {
            lot.append(inv.getReceiveLot() + "|");
        }

        if (ObjectUtil.isNotNull(inv.getGmtManufacture())) {
            lot.append(dtf.format(inv.getGmtManufacture()) + "|");
        }

        if (ObjectUtil.isNotNull(inv.getGmtExpire())) {
            lot.append(dtf.format(inv.getGmtExpire()) + "|");
        }

        if (ObjectUtil.isNotNull(inv.getGmtStock())) {
            lot.append(dtf.format(inv.getGmtStock()) + "|");
        }

        if (StrUtil.isNotBlank(inv.getSerialNumber())) {
            lot.append(inv.getSerialNumber() + "|");
        }

        if (StrUtil.isNotBlank(inv.getSupplier())) {
            lot.append(inv.getSupplier() + "|");
        }

        if (StrUtil.isNotBlank(inv.getExtendOne())) {
            lot.append(inv.getExtendOne() + "|");
        }

        if (StrUtil.isNotBlank(inv.getExtendTwo())) {
            lot.append(inv.getExtendTwo() + "|");
        }

        if (ObjectUtil.isNotNull(inv.getExtendThree())) {
            lot.append(inv.getExtendThree().toString() + "|");
        }

        if (ObjectUtil.isNotNull(inv.getExtendFour())) {
            lot.append(inv.getExtendFour().toString() + "|");
        }

        if (ObjectUtil.isNotNull(inv.getExtendFive())) {
            lot.append(dtf.format(inv.getExtendFive()) + "|");
        }

        if (ObjectUtil.isNotNull(inv.getExtendSix())) {
            lot.append(dtf.format(inv.getExtendSix()) + "|");
        }

        // 去除最后一个竖杠，由于gmtStock肯定存在，所以不用担心字段为空报错
        lot.deleteCharAt(lot.length() - 1);

        return lot.toString();
    }

    /**
     * 生成由所有扩展字段组成的库存批次号，方便判断是否有相同库存
     * @param inv
     * @return
     */
    public static String generatorFullLot(Inventory inv) {
        StringBuilder fullLot = new StringBuilder();
        // 生产日期、过期日期、存货日期记录年月日，去除时分秒
        String gmtManufactureStr = inv.getGmtManufacture()==null?StrUtil.EMPTY:inv.getGmtManufacture().toLocalDate().toString();
        String gmtExpireStr = inv.getGmtExpire()==null?StrUtil.EMPTY:inv.getGmtExpire().toLocalDate().toString();
        String gmtStockStr = inv.getGmtStock()==null?StrUtil.EMPTY:inv.getGmtStock().toLocalDate().toString();

        // 扩展3、4为数字类型，null时设为空
        String extendThreeStr = inv.getExtendThree()==null?StrUtil.EMPTY:inv.getExtendThree().toString();
        String extendFourStr = inv.getExtendFour()==null?StrUtil.EMPTY:inv.getExtendFour().toString();

        // 扩展5、6的日期，暂定包含时分秒
        String extendFiveStr = inv.getExtendFive()==null?StrUtil.EMPTY:inv.getExtendFive().toString();
        String extendSixStr = inv.getExtendSix()==null?StrUtil.EMPTY:inv.getExtendSix().toString();

        // 库存批次，由12个字段组成
        fullLot.append(StrUtil.blankToDefault(inv.getReceiveLot(), StrUtil.EMPTY) + "|");
        fullLot.append(gmtManufactureStr + "|");
        fullLot.append(gmtExpireStr + "|");
        fullLot.append(gmtStockStr + "|");
        fullLot.append(StrUtil.blankToDefault(inv.getSerialNumber(), StrUtil.EMPTY) + "|");
        fullLot.append(StrUtil.blankToDefault(inv.getSupplier(), StrUtil.EMPTY) + "|");
        fullLot.append(StrUtil.blankToDefault(inv.getExtendOne(), StrUtil.EMPTY) + "|");
        fullLot.append(StrUtil.blankToDefault(inv.getExtendTwo(), StrUtil.EMPTY) + "|");
        fullLot.append(extendThreeStr + "|");
        fullLot.append(extendFourStr + "|");
        fullLot.append(extendFiveStr + "|");
        fullLot.append(extendSixStr );

        return fullLot.toString();
    }


    /**
     * 根据前缀，获取自增序号
     *
     * @param prefix
     * @return
     */
    public static String getAutoIncrementIndex(String prefix) {
        Long num = RedisUtil.incr(prefix);
        if (null == num) {
            //@todo redis返回异常数据，需要后期备灾处理，例如查询数据库等
            String msg = "redis服务器返回自增序号异常";
            log.info(LogUtil.log("orderIdGenerator", msg));
            throw new ServiceException(msg);
        }
        if (num < 7) {
            RedisUtil.expire(prefix, EXPIRE_SECONDS);
        }

        return String.format("%0" + 7 + "d", num);
    }
    /**
     * 根据前缀，获取自增序号
     *
     * @param prefix
     * @param length 流水号长度
     * @return
     */
    public static String getAutoIncrementIndex(String prefix,int length) {
        Long num = RedisUtil.incr(prefix);
        if (null == num) {
            //@todo redis返回异常数据，需要后期备灾处理，例如查询数据库等
            String msg = "redis服务器返回自增序号异常";
            log.info(LogUtil.log("orderIdGenerator", msg));
            throw new ServiceException(msg);
        }
        if (num < length) {
            RedisUtil.expire(prefix, EXPIRE_SECONDS);
        }

        return String.format("%0" + length + "d", num);
    }
}
